package net.disy.cadenza.repository.parser;

import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import net.disy.cadenza.associations.schema.AssociationList;
import net.disy.cadenza.dbmapping.schema.DatabaseMapping;
import net.disy.cadenza.objecttypes.schema.CadenzaObjectType;
import net.disy.cadenza.objecttypes.schema.ObjectTypeList;
import net.disy.cadenza.repository.workflow.Workflow;
import net.disy.cadenza.repository.wrappers.ObjectTypeCatalogWrapper;
import net.disy.cadenza.repository.wrappers.ObjectTypeListWrapper;
import net.disy.cadenza.repository.wrappers.SelectorListWrapper;
import net.disy.cadenza.repository.wrappers.SelectorWrapper;
import net.disy.cadenza.repositorytree.schema.Folder;
import net.disy.cadenza.schema.datasources.DataSources;
import net.disy.cadenza.schema.objecttypecatalog.Location;
import net.disy.cadenza.schema.objecttypecatalog.ObjectTypeCatalog;
import net.disy.cadenza.schema.selection.Contains;
import net.disy.cadenza.schema.selection.Default;
import net.disy.cadenza.schema.selection.Selector;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class RepositoryParser {
	private static RepositoryParser repositoryParser;
	private String xmlRepositoryPath;
	private Unmarshaller objectTypesUnmarshaller;
	private Unmarshaller associationsUnmarshaller;
	private Unmarshaller objectTypeCatalogUnmarshaller;
	private Unmarshaller databaseMappingUnmarshaller;
	private Unmarshaller dataSourcesUnmarshaller;
	private Unmarshaller repositoryTreeUnmarshaller;
	private Unmarshaller selectionUnmarshaller;
	
	public static RepositoryParser getInstance(String xmlRepositoryPath) throws JAXBException{
		if(repositoryParser == null)
			repositoryParser = new RepositoryParser(xmlRepositoryPath);
		return repositoryParser; 
	}
	
	private RepositoryParser(String xmlRepositoryPath) throws JAXBException{
		this.xmlRepositoryPath = xmlRepositoryPath;
		objectTypesUnmarshaller = JAXBContext.newInstance("net.disy.cadenza.objecttypes.schema").createUnmarshaller();
		associationsUnmarshaller = JAXBContext.newInstance("net.disy.cadenza.associations.schema").createUnmarshaller();
		objectTypeCatalogUnmarshaller = JAXBContext.newInstance("net.disy.cadenza.schema.objecttypecatalog").createUnmarshaller();
		databaseMappingUnmarshaller = JAXBContext.newInstance("net.disy.cadenza.dbmapping.schema").createUnmarshaller();
		dataSourcesUnmarshaller = JAXBContext.newInstance("net.disy.cadenza.schema.datasources").createUnmarshaller();
		repositoryTreeUnmarshaller = JAXBContext.newInstance("net.disy.cadenza.repositorytree.schema").createUnmarshaller();
		selectionUnmarshaller = JAXBContext.newInstance("net.disy.cadenza.schema.selection").createUnmarshaller();
		
	}
	
	public ObjectTypeCatalogWrapper parseObjectTypeCatalog() throws XMLStreamException, JAXBException, IOException{
		XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(new FileInputStream(xmlRepositoryPath + RepositoryConstants.OBJECT_TYPE_CATALOG_FILE));
		JAXBElement<ObjectTypeCatalog> jaxbElement =  (JAXBElement<ObjectTypeCatalog>)objectTypeCatalogUnmarshaller.unmarshal(reader, ObjectTypeCatalog.class);
		ObjectTypeCatalog objectTypeCatalog = (ObjectTypeCatalog)jaxbElement.getValue();
		ObjectTypeCatalogWrapper objectTypeCatalogWrapper = new ObjectTypeCatalogWrapper(objectTypeCatalog);
		for(Location objectTypeLocation : objectTypeCatalog.getObjectTypes().getLocation()){
			ObjectTypeListWrapper objectTypeList = parseObjectTypeList(xmlRepositoryPath +  RepositoryConstants.PATH_OBJECT_TYPES_PATH + objectTypeLocation.getPath());
			objectTypeCatalogWrapper.addObjectTypeList(objectTypeList);
		}
		for(Location associationLocation : objectTypeCatalog.getAssociations().getLocation()){
			AssociationList associationList = parseAssociationList(xmlRepositoryPath + RepositoryConstants.PATH_OBJECT_TYPES_PATH + associationLocation.getPath());
			objectTypeCatalogWrapper.addAssociationList(associationList);
		}
		reader.close();
		return objectTypeCatalogWrapper;
	}
	
	public DataSources parseDataSources() throws XMLStreamException, JAXBException, IOException{
		XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(new FileInputStream(xmlRepositoryPath + RepositoryConstants.DATA_SOURCES_FILE));
		JAXBElement<DataSources> jaxbElement =  (JAXBElement<DataSources>)dataSourcesUnmarshaller.unmarshal(reader, DataSources.class);
		DataSources dataSources = (DataSources)jaxbElement.getValue();
		reader.close();
		return dataSources;
	}
	
	public Folder parseRepositoryTree() throws XMLStreamException, JAXBException, IOException{
		XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(new FileInputStream(xmlRepositoryPath + RepositoryConstants.REPOSITORY_TREE_FILE));
		JAXBElement<Folder> jaxbElement =  (JAXBElement<Folder>)repositoryTreeUnmarshaller.unmarshal(reader, Folder.class);
		Folder repositoryTreeRootFolder = (Folder)jaxbElement.getValue();
		reader.close();
		return repositoryTreeRootFolder;
	}
	
	public SelectorListWrapper parseSelectors() throws XMLStreamException, JAXBException, IOException{
		Map<String, SelectorWrapper> nameToSelectorMap = new HashMap<String, SelectorWrapper>();
		File selectorsDir = new File(xmlRepositoryPath + RepositoryConstants.SELECTORS_PATH );
		String[] selectorFileNames = selectorsDir.list(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				if(name.endsWith(".sel"))
					return true;
				return false;
			}
		});
		
		for(String selectorFileName : selectorFileNames){
			SelectorWrapper selectorWrapper = parseSelector(selectorFileName);
			nameToSelectorMap.put(selectorWrapper.getName(), selectorWrapper);
		}
		
//		for(SelectorWrapper selector : nameToSelectorMap.values()){
//			if(!selector.isBaseSelector() && selector.getBaseSelector() == null){
//				selector.setBaseSelector(nameToSelectorMap.get(selector.getBaseSelectorName()));
//			}
//		}
		
		return new SelectorListWrapper(nameToSelectorMap);
	}
	
	public Workflow parseWorkflow(String workflowFileName){
		Workflow workflow = null;
		try{
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			factory.setNamespaceAware(false);
			
			FileInputStream fis = new FileInputStream(new File(xmlRepositoryPath + RepositoryConstants.WORKFLOWS_PATH + workflowFileName));
			DocumentBuilder documentBuilder = factory.newDocumentBuilder();
			Document document = documentBuilder.parse(fis);
			
			workflow = new Workflow(document, workflowFileName);
			
		}catch (ParserConfigurationException e) {
			e.printStackTrace();
		}catch (SAXException e) {
			e.printStackTrace();
		}catch (IOException e) {
			e.printStackTrace();
		}
		return workflow;
	}
	
	private SelectorWrapper parseSelector(String selectorFileName) throws XMLStreamException, JAXBException, IOException{
		XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(new FileInputStream(xmlRepositoryPath + RepositoryConstants.SELECTORS_PATH + selectorFileName));
		JAXBElement<Selector> jaxbElement =  (JAXBElement<Selector>)selectionUnmarshaller.unmarshal(reader, Selector.class);
		Selector selector = (Selector)jaxbElement.getValue();
		if(selector.getExtension() != null){
			String baseSelector = selector.getExtension().getOtherAttributes().values().iterator().next();
			String baseSelectorName = baseSelector.substring(baseSelector.indexOf(":") + 1, baseSelector.length()) + ".sel";
			SelectorWrapper baseSelectorWrapper = parseSelector(baseSelectorName);
			
			Map<String, List<Default>> containsConditionsDefaults = new HashMap<String, List<Default>>();
			for(Element element : selector.getExtension().getAny()){
				JAXBElement<Contains> containsJaxbElement =  (JAXBElement<Contains>)selectionUnmarshaller.unmarshal(element, Contains.class);
				Contains containsCondition = (Contains)containsJaxbElement.getValue();
				List<Default> defaultList = new ArrayList<Default>();
				NodeList defaultElementList = element.getElementsByTagName("default");
				for(int i = 0 ; i < defaultElementList.getLength(); i++){
					Element defaultElement = (Element)defaultElementList.item(i);
					JAXBElement<Default> defaultJaxbElement =  (JAXBElement<Default>)selectionUnmarshaller.unmarshal(defaultElement, Default.class);
					defaultList.add((Default)defaultJaxbElement.getValue());
					
				}
				containsConditionsDefaults.put(containsCondition.getConditionName(), defaultList);
			}
			
			SelectorWrapper selectorWrapper = new SelectorWrapper(selector, containsConditionsDefaults , selectorFileName, baseSelectorWrapper);
			reader.close();
			return selectorWrapper;
			
		}
		
		SelectorWrapper selectorWrapper = new SelectorWrapper(selector, selectorFileName);
		reader.close();
		return selectorWrapper;
	}
	
	private ObjectTypeListWrapper parseObjectTypeList(String fileName) throws XMLStreamException, JAXBException, IOException{
		XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(new FileInputStream(fileName));
		JAXBElement<ObjectTypeList> jaxbElement =  (JAXBElement<ObjectTypeList>)objectTypesUnmarshaller.unmarshal(reader, ObjectTypeList.class);
		ObjectTypeList objectTypeList = (ObjectTypeList)jaxbElement.getValue();
		for(CadenzaObjectType cadenzaObjectType : objectTypeList.getObjectType()){
			Node dbMappingNode = (Node)cadenzaObjectType.getMappings().getAny();
			cadenzaObjectType.getMappings().setAny(parseDatabaseMappingNode(dbMappingNode));
		}
		reader.close();
		return new ObjectTypeListWrapper(objectTypeList);
	}
	
	private AssociationList parseAssociationList(String fileName) throws XMLStreamException, JAXBException, IOException{
		XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(new FileInputStream(fileName));
		JAXBElement<AssociationList> jaxbElement =  (JAXBElement<AssociationList>)associationsUnmarshaller.unmarshal(reader, AssociationList.class);
		AssociationList associationList = (AssociationList)jaxbElement.getValue();
		reader.close();
		return associationList;
	}
	
	private DatabaseMapping parseDatabaseMappingNode(Node dbMappingNode) throws JAXBException {
		return databaseMappingUnmarshaller.unmarshal(dbMappingNode, DatabaseMapping.class).getValue();
	}
}
